package cn.taskservice.redis.limit.aspect;

import cn.taskservice.common.utils.ip.IpUtil;
import cn.taskservice.redis.limit.annotation.RateLimit;
import cn.taskservice.redis.limit.enums.LimitTypeEnums;
import cn.taskservice.redis.lock.util.SpELParserUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;

@Slf4j
@Aspect
@Component
@Order(1)
public class LimitAspect {
    @Resource
    private RedisTemplate<String, Serializable> redisTemplate;

    @Resource(name = "limitScript")
    private DefaultRedisScript<Long> limitScript;

    @Resource
    private SpELParserUtil spelParserUtil;

    @Pointcut("@annotation(cn.taskservice.redis.limit.annotation.RateLimit)")
    public void limitPointCut() {
    }

    @Before("limitPointCut()")
    public void before(JoinPoint joinPoint) {
        String key;
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            RateLimit limitPoint = method.getAnnotation(RateLimit.class);
            if (limitPoint == null) {
                // 如果注解在接口上
                method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
                limitPoint = method.getAnnotation(RateLimit.class);
            }
            // 基础信息
            int period = limitPoint.period();
            int limit = limitPoint.limit();
            // 限流 key 生成
            if (limitPoint.limitType() == LimitTypeEnums.IP) {
                String ip = IpUtil.getIpAddress(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
                key = limitPoint.key() + ":" + ip;
            } else {
                key = spelParserUtil.parse(limitPoint.key(), method, joinPoint.getArgs());
            }
            String redisKey = limitPoint.prefix() + key;
            Long count = redisTemplate.execute(limitScript, Collections.singletonList(redisKey), limit, period);
            if (count != null && count > limit) {
                log.warn("限流拦截：key={}, 当前请求次数={}, 限制次数={}", redisKey, count, limit);
                throw new RuntimeException("访问过于频繁，请稍后再试");
            }
        } catch (RuntimeException e) {
            log.error("限流脚本执行异常", e);
            throw new RuntimeException("限流执行异常");
        }catch (Exception e) {
            log.error("未知异常，限流失败", e);
            throw new RuntimeException("服务繁忙，请稍后重试");
        }
    }
}
